import MsgBox, { MsgBoxType, MsgBoxInfo } from "../prefabs/msg_box";
import BGMManager from "./bgm_manager";
import GameTask from "./game_task";
import Macro from "./macro";
import MsgHub, { SubjectComponent } from "./subject";
import Utils from "./utils";


const { ccclass, property } = cc._decorator;

export enum ReplacePageAni {
    None,
    Fade
};

export class MsgReplacePage {
    name = "";
    ani = ReplacePageAni.None;
};

export class MsgPushNode {
    prefab: cc.Node | cc.Prefab = null;
    baseOn: "page" | "boxContainer" | "persist" = "page";
};

export enum PageContainStage {
    Idle,
    Deleting,
    Adding,
};

export class DelayCaller {
    private __caller: any = null;
    private __calls: any[] = [];
    init(caller: any) {
        this.__caller = caller;
    }
    addCall(cbName: string, ...args: any[]): any {
        this.__calls.push([cbName, args]);
    }
    next() {
        if (this.__calls.length > 0) {
            let [cbName, args] = this.__calls.shift();
            return this.__caller[cbName](...args);
        }
    }
};

@ccclass
export default class Game extends SubjectComponent {
    dcaller: DelayCaller = new DelayCaller();
    gameTaskList: GameTask[] = [];
    @property(cc.Camera)
    sceneCamera: cc.Camera = null;
    @property(cc.Node)
    container: cc.Node = null;
    containStage: PageContainStage = PageContainStage.Idle;
    @property(cc.Node)
    boxContainer: cc.Node = null;
    @property()
    startPageName = "";
    @property(cc.Label)
    versionLb: cc.Label = null;
    curPage: cc.Node = null;
    gameStarted = false;

    onDestroy() {
        MsgHub.off(this);
        Utils.game = null;
    }
    onLoad() {
        Utils.game = this;
        this.versionLb.node.active = (Macro.BUILD_TARGET == "dev" || (typeof window["SHOW_VERSION"] !== "undefined" && window["SHOW_VERSION"]));
        this.versionLb.string = `${Macro.APP_VERSION}`;
        if (cc.dynamicAtlasManager) {
            cc.dynamicAtlasManager.enabled = false;
        }
        MsgHub.on(MsgReplacePage, this.replacePage, this);
        MsgHub.on(Macro.EVENTS.PUSH_BOX, this.pushBox, this);
        MsgHub.on(MsgPushNode, this.pushNode, this);
        this.boxContainer.on(cc.Node.EventType.CHILD_ADDED, this.onBoxChanged, this);
        this.boxContainer.on(cc.Node.EventType.CHILD_REMOVED, this.onBoxChanged, this);

    }
    addGameTask(taskCtor: new () => GameTask) {
        let task = this.addComponent(taskCtor);
        this.gameTaskList.push(task);
        return task;
    }
    runAllGameTask() {
        return new Promise<void>(async ok => {
            this.gameTaskList = this.getComponentsInChildren(GameTask);
            this.gameTaskList.forEach(task => {
                task.once("finished", () => {
                    if (this.isAllBeforeTaskDone()) {
                        ok();
                    }
                }, this);
            });
            for (let i = 0; i < this.gameTaskList.length; i++) {
                let task = (this.gameTaskList[i]);
                if (task.isSync) {
                    await task.init();
                }
                else {
                    task.init();
                }
            }
        });
    }

    onBoxChanged() {
        if (this.boxContainer.childrenCount === 0) {
            BGMManager.ins.delGrille("box");
        }
        else {
            BGMManager.ins.addGrille("box", 0.4);
        }
    }

    isAllBeforeTaskDone() {
        let list = this.gameTaskList.filter(task => task.isBeforeGame);
        for (let i = 0; i < list.length; i++) {
            if (!list[i].isDone) {
                return false;
            }
        }
        return true;
    }
    startGame() {
        if (!this.isAllBeforeTaskDone()) {
            return;
        }
        if (this.gameStarted) {
            return;
        }
        for (let key in Macro.BUNDLE_VERSION) {
            this.versionLb.string += ` [${key}:${Macro.BUNDLE_VERSION[key]}]`;
        }
        this.gameStarted = true;
        let pageName = this.startPageName;
        this.replacePage({
            name: pageName,
            ani: ReplacePageAni.None
        });
    }
    histroy: string[] = [];
    async prevPage(ani: ReplacePageAni) {
        if (this.histroy.length === 0) {
            console.warn("Game::prevPage, warn: 没有上一页");
            return;
        }
        let pageName = this.histroy.shift();

        this.replacePage({
            name: pageName,
            ani: ani
        });
    }
    async replacePage(msg: MsgReplacePage) {
        let prefab: cc.Prefab = Utils.loader.getAsset(msg.name, "pages");
        if (!prefab) {
            cc.log(`没找到页面 ${msg.name}`);
            return;
        }

        if (this.containStage !== PageContainStage.Idle) {
            // console.wan("Game::replacePage, 页面容器正在切换.");
            //加入到任务中。
            this.dcaller.addCall("replacePage", msg);
            return;
        }

        let oldPageName = "无页面";
        let curPageName = "无页面";
        let oldPage = this.curPage;
        var page = cc.instantiate(prefab);
        this.curPage = page;
        curPageName = this.curPage.name;


        if (cc.isValid(oldPage)) {
            oldPageName = oldPage.name;
            this.histroy.unshift(oldPageName);
            this.containStage = PageContainStage.Deleting;
            await new Promise<void>(ok => {
                if (msg.ani === ReplacePageAni.Fade) {
                    cc.Tween.stopAllByTarget(oldPage);
                    cc.tween(oldPage).to(0.5, { opacity: 0 }).call(oldPage.destroy.bind(oldPage)).call(ok).start();
                }
                else {
                    oldPage.destroy();
                    cc.director.once(cc.Director.EVENT_AFTER_UPDATE, () => {
                        ok();
                    });
                }
            });
        }

        this.containStage = PageContainStage.Adding;

        await new Promise<void>(ok => {
            if (msg.ani === ReplacePageAni.Fade) {
                this.container.addChild(page);
                this.curPage.opacity = 0;
                cc.tween(this.curPage).to(0.5, { opacity: 255 }).call(ok).start();
            }
            else {
                this.container.addChild(page);
                cc.director.once(cc.Director.EVENT_AFTER_UPDATE, () => {
                    ok();
                });
            }
        });

        this.containStage = PageContainStage.Idle;

        this.emit("replace-page", { prevPage: oldPage, curPage: this.curPage });
        console.log("replace-page", `(${oldPageName} -> ${curPageName})`);
        this.dcaller.next();
        return this.curPage;
    }
    pushBox(name: string) {
        let prefab: cc.Prefab = Utils.loader.getAsset(name, "boxes");
        if (!prefab) {
            cc.log(`没找到Box ${name}`);
            return;
        }
        let box = cc.instantiate(prefab);
        this.boxContainer.addChild(box);
        return box;
    }
    pushNode(mpn: MsgPushNode): cc.Node {
        let prefab = mpn.prefab;
        if (!prefab) {
            return;
        }
        let baseOn = mpn.baseOn;

        let node = null;
        if (prefab instanceof cc.Prefab) {
            node = cc.instantiate(prefab);
        }
        else {
            node = prefab;
        }
        switch (baseOn) {
            case "page":
                if (!cc.isValid(this.curPage)) {
                    console.warn("Game::resPushNode, warn: 没有curPage");
                    return;
                }
                this.curPage.addChild(node);
                break;
            case "boxContainer":
                this[baseOn].addChild(node);
                break;
            case "persist":
                this.node.parent.addChild(node);
                break;
        }

        return node;
    }

    get msgBoxPrefab() {
        return Utils.loader.getAsset<cc.Prefab>("msg_box", "boxes", "cc.Prefab");
    }
    msgBox(text: string, title?: string, type: MsgBoxType = MsgBoxType.Ok) {
        let info = new MsgBoxInfo();
        info.text = text;
        info.title = title;
        info.type = type;

        let node = cc.instantiate(this.msgBoxPrefab);
        let box = node.getComponent(MsgBox);
        box.setInfo(info);
        this.boxContainer.addChild(node);

        return box;
    }

    msgBoxSync(text: string, title?: string) {
        return new Promise<void>(ok => {
            let info = new MsgBoxInfo();
            info.text = text;
            info.title = title;
            info.type = MsgBoxType.Ok;

            let node = cc.instantiate(this.msgBoxPrefab);
            let box = node.getComponent(MsgBox);
            box.setInfo(info);
            this.boxContainer.addChild(node);
            box.once(Macro.EVENTS.MSG_BOX_OK, ok, this);
        });
    }
    msgBoxYesNoSync(text: string, title?: string) {
        return new Promise<boolean>(ok => {
            this.msgBox(text, title, MsgBoxType.YesNo).setYesNoCallBack(
                () => {
                    ok(true);
                },
                () => {
                    ok(false);
                }
            );
        });
    }
};